﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.DirectX;
using GameEngine.View.Components;

namespace MapEngine
{
    public delegate List<Shape> CreateShapesDelegate(Tile tile);

    public partial class TileMapUC : UserControl
    {
        public IDictionary<Point, Tuple<Tile,List<Shape>>>Tiles
        {
            get;
            set;
        }
        Point MapSize;
        public TileBuilder TileBuilder;
        public Point Position;
        Point ScreenPosition;
        //List<List<float>> HeightMap = new List<List<float>>();
        TranslateTransform Translate;
        Point TileSize;
        CreateShapesDelegate CreateShapes;

        public TileType TileType
        {
            get;
            set;
        }

        public MapType MapType
        {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="heightMap">List of points organized in Rows(y)</param>
        /// <param name="center"></param>
        /// <param name="tileSize"></param>
        public TileMapUC(GetHeightDelegate getHeight, Point tileSize, CreateShapesDelegate CreateShapes, Point mapSize, Point screenPosition, TileType type)
        {
            InitializeComponent();
            Tiles = new Dictionary<Point, Tuple<Tile,List<Shape>>>();
            TileType = type;
            TileBuilder = new TileBuilder(tileSize, getHeight);
            MapSize = mapSize;
            TileSize = tileSize;
            TransformGroup tg = new TransformGroup();
            Translate = new TranslateTransform();
            tg.Children.Add(Translate);
            Map.RenderTransform = tg;
            ScreenPosition = screenPosition;
            Position = new Point();
            //UpdateMapPosition();
            this.CreateShapes = CreateShapes;

            //Map.Children.Add(new );

            /*for (int i = 0; i < MapSize.Y; i++)
            {
                AddRow(1, (int)MapSize.X);
            }*/
            
            /*for (int row = -(int)mapSize.Y/2; row < MapSize.Y/2; row++) {
                for (int column = -(int)mapSize.X/2; column < MapSize.X/2; column++)
                {
                    AddTile(row,column);
                }
            }*/
            UpdateTiles();
        }
        
        public Point Transform(Point point, float height) {
            switch(MapType){
                case MapType.Isometric:
                    return IsometricMath.PointFrom3d(new Point3d(
                        new Point(point.X, -point.Y),
                        height
                        ));
                case MapType.Top:
                    point.Y = point.Y;
                    return point;
            }
            return new Point();
        }
        
        int negativeRows = 0;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="direction">1-top || -1-bottom</param>
        /// <param name="rowHeights"></param>
        void AddRow(int direction, int size)
        {
            int rowIndex = direction > 0 ? Tiles.Count : 0;
            for (int columnIndex = 0; columnIndex < size; columnIndex++)
            {
                //add to the screen
                AddTile(new Point(columnIndex + negativeColumns,rowIndex + negativeRows));
            }
            //add to the list
            //Tiles.Insert(rowIndex, tileRow);
            if (direction < 0)
                negativeRows--;
        }

        int negativeColumns = 0;
        void AddColumn(int direction, int size)
        {
            for (int rowIndex = 0; rowIndex < size; rowIndex++)
            {
                int columnIndex = direction > 0 ? (int)MapSize.X : 0;
                //add to the screen
                AddTile(new Point(columnIndex + negativeColumns,rowIndex + negativeRows));
            }
            if (direction < 0)
                negativeColumns--;
        }

        void RemoveRow(int direction)
        {
            int index = direction > 0 ? Tiles.Count-1 : 0;
            List<Point> points = new List<Point>();
            foreach (KeyValuePair<Point,Tuple<Tile,List<Shape>>> tile in Tiles)
            {
                if (tile.Value.Item1.MapPosition.Y == index)
                    points.Add(tile.Value.Item1.MapPosition);
            }
            foreach(Point point in points)
                RemoveTile(point);
            if (direction < 0)
                negativeRows++;
        }

        void RemoveColumn(int direction)
        {
            for (int rowIndex = 0; rowIndex < MapSize.X; rowIndex++)
            {
                int columnIndex = direction > 0 ? (int)MapSize.X-1 : 0;
                RemoveTile(new Point(columnIndex, rowIndex));
            }
            if (direction < 0)
                negativeColumns++;
        }
        
        /// <summary>
        /// Transform position from map coordinates to screen position, without map transformations
        /// </summary>
        /// <param name="MapPosition"></param>
        /// <param name="height"></param>
        /// <returns></returns>
        public Point TransformMapPositionToScreenPosition(Point MapPosition, float height)
        {
            Point point = new Point(
               TileBuilder.TileSize.X * MapPosition.X,
               TileBuilder.TileSize.Y * MapPosition.Y
            );
            point = Transform(point, height);
            return point; 
        }

        public Point? TransformScreenPositionToMapPosition(Point point, out float height) {
             /*point.X /= TileSize.X;
            point.Y /= TileSize.Y;
            point.X -= Position.X;
            point.Y -= Position.Y;
            foreach (Tile tile in Tiles) {
                foreach (Shape shape in tile.Shapes) { 
                shape.
                }
            }*/
            GeneralTransform generalTransform = Map.TransformToVisual(Application.Current.RootVisual);
            Point pnts = generalTransform.Transform(point);

            List<UIElement> hits = System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(pnts, Map) as List<UIElement>;
            if (hits.Count > 0)
            {
                foreach (KeyValuePair<Point,Tuple<Tile,List<Shape>>> tile in Tiles)
                {
                    foreach (Shape shape in tile.Value.Item2)
                    {
                        if (hits.Contains(shape))
                        {
                            height = tile.Value.Item1.Height;
                            return tile.Value.Item1.MapPosition;
                        }
                    }
                }
            }
            height = 0;
            return null;
        }

        /// <summary>
        /// Creates a tile with the center in the left down corner
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public Tile AddTile(Point position)
        {
            RemoveTile(position);
            Tile tile = TileBuilder.CreateTile(position, TileType);
            List<Shape> shapes = CreateShapes(tile);
            Tiles.Add(position, Tuple.Create<Tile,List<Shape>>(tile,shapes));
            foreach (Shape shape in shapes)
            {
                Map.Children.Add(shape);
            }
            return tile;
        }

        public void RemoveTile(Point position)
        {
            List<Shape> shapes = GetShapes(position);
            if (shapes != null)
            {
                foreach (Shape shape in shapes)
                {
                    shape.MouseMove -= null;

                    Map.Children.Remove(shape);
                }
                Tiles.Remove(position);
            }
        }

        Tile GetTile(Point position) {
            if(Tiles.ContainsKey(position))
                return Tiles[position].Item1;
            /*
            foreach (Tile tileFound in Tiles)
            {
                if (tileFound.MapPosition.Equals(position))
                    return tileFound;
            }*/
            return null;
        }

        List<Shape> GetShapes(Point position)
        {
            if (Tiles.ContainsKey(position))
                return Tiles[position].Item2;
            /*
            foreach (Tile tileFound in Tiles)
            {
                if (tileFound.MapPosition.Equals(position))
                    return tileFound;
            }*/
            return null;
        }

        public void UpdateTiles() {
            for (int x = 0; x < MapSize.X; x++)
            {
                for (int y = 0; y < MapSize.Y; y++)
                {
                    AddTile(new Point(x, y));
                }
            }
        }

        public void UpdateTile(Point position){
            List<Point> positions = new List<Point>();
            positions.Add(position);
            //if height changed alterar os tiles à volta
            Point sTile = position; sTile.Y--;  positions.Add(sTile);
            Point nTile = position; nTile.Y++;  positions.Add(nTile);
            Point wTile = position; wTile.X--;  positions.Add(wTile);
            Point eTile = position; eTile.X++;  positions.Add(eTile);
            Point seTile = position;    seTile.Y--; seTile.X++;  positions.Add(seTile);
            Point swTile = position;    swTile.Y--; swTile.X--;  positions.Add(swTile);
            Point neTile = position;    neTile.Y++; neTile.X++;  positions.Add(neTile);
            Point nwTile = position;    nwTile.Y++; nwTile.X--;  positions.Add(nwTile);
            
            foreach(Point p in positions){
                Tile tile = GetTile(p);
                if (tile != null)
                {
                    AddTile(p);    
                }
            }
        }

        //public Tile getTileFromTileMapCoordinates(Point tilePosition)
        //{
        //    return Tiles[(int)tilePosition.Y][(int)tilePosition.X];
        //}

        //public Tile getTileFromScreenCoordinates(Point screenPosition)
        //{
        //    return null;//TODO
        //    /*
        //    foreach (List<Tile> tileRow in Tiles) {
        //        foreach (Tile tile in tileRow) { 
        //            if(tile.Position
        //        }
        //    }
        //    return Tiles[(int)tilePosition.Y][(int)tilePosition.X];*/
        //}
        
        public void MoveMap(int n, int e, int s, int w)
        {
            switch (MapType) { 
                case MapType.Top:

                    for (int i = 0; i < n; i++)
                    {
                        AddRow(1, (int)MapSize.X);
                        RemoveRow(-1);
                    }

                    for (int i = 0; i < e; i++)
                    {
                        AddColumn(1, (int)MapSize.Y);
                        RemoveColumn(-1);
                    }

                    for (int i = 0; i < s; i++)
                    {
                        AddRow(-1, (int)MapSize.X);
                        RemoveRow(1);
                    }

                    for (int i = 0; i < w; i++)
                    {
                        AddColumn(-1, (int)MapSize.Y);
                        RemoveColumn(1);
                    }

                    Position.X -= e;
                    Position.X += w;
                    Position.Y += s;
                    Position.Y -= n;
  
                    break;
                case MapType.Isometric:
                    
                    // Add / and \  row top and column right
                    for (int i = 0; i < n; i++)
                    {
                        AddRow(1, (int)MapSize.X);
                        RemoveRow(-1);

                        AddColumn(-1, (int)MapSize.Y);
                        RemoveColumn(1);
                    }
                    // Add \ and /  column right and row top
                    for (int i = 0; i < e; i++)
                    {
                        AddColumn(1, (int)MapSize.Y);
                        RemoveColumn(-1);

                        AddRow(1, (int)MapSize.X);
                        RemoveRow(-1);
                    }

                    // Add / and \ row bottom and column left
                    for (int i = 0; i < s; i++)
                    {
                        AddRow(-1, (int)MapSize.X);
                        RemoveRow(1);

                        AddColumn(1, (int)MapSize.Y);
                        RemoveColumn(-1);
                    }
                    // Add / and \ row bottom and column left
                    for (int i = 0; i < w; i++)
                    {
                        AddColumn(-1, (int)MapSize.Y);
                        RemoveColumn(1);

                        AddRow(-1, (int)MapSize.X);
                        RemoveRow(1);
                    }
                    
                    Position.X -= e;
                    Position.Y -= e;

                    Position.X += w;
                    Position.Y += w;

                    Position.X += s;
                    Position.Y -= s;

                    Position.X -= n;
                    Position.Y += n;
                    break;
            }
        }
    }
}